iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
Modern Web

一些讓你看來很強的 ORM - prisma系列 第 18

Day18. 一些讓你看來很強的 ORM - prisma (zod validate)

  • 分享至 

  • xImage
  •  

今天要來介紹 prismaextension ,前幾天我們透過 Prisma.validator 讓我們的 prismaqueriestype safe 的功能,但其實還有別種 customer 的方式,今天我們將介紹如何使用 zod 去幫我們確保 prismaquery input ,那我們廢話不多說走起~

Demo

以下是今天的 model

  • user 可以有多個 post
  • email model 去紀錄有使用過的 email 有哪些
model User {
  id        Int    @id @default(autoincrement())
  email     String
  firstName String
  lastName  String
  posts     Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  published Boolean @default(true)
  content   String?
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model Email {
  id         Int      @id @default(autoincrement())
  value      String   @unique
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt
}

之後 migrateDB 後我們先到 prisma studio 塞一些 email data ,這邊得 email list 的用處會是等下用來驗證 user createemail

https://ithelp.ithome.com.tw/upload/images/20241002/201456774SB7EElxJB.png

接著我們寫一個 getEmails function 去拿到所有合法的 email list

const getEmails = async () => {
  const emails = await prismaClient.email.findMany({})
  return emails
}

結果如下,可以看到這邊清楚記錄合法 email 存在的時間以及修改的時間

[
  {
    id: 1,
    value: 'hiunji64@gmail.com',
    created_at: 2024-10-02T14:02:21.987Z,
    updated_at: 2024-10-02T14:02:13.491Z
  }
]

之後我們 install zod

>npm i zod                                  

接著我們定好 user createzod schema:

  • firstName 為必填
  • lastName 為必填
  • email 必須是 email 的格式,同時 value 只能用 getEmails returnemail list
const userCreateInputSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
  email: z
    .string()
    .email('this is not validate emails')
    .refine(async (email) => {
      const emails = await getEmails()
      return emails.findIndex(({ value }) => value === email) !== -1
    }, 'This email is not in our database')
})

Prisma Client Extension

現在我們做好了一個合法的 email list ,也定好了 input schema ,之後我們需要跟 prisma client 溝通,以確保我們的 query 可以透過我們定好的 schema 去驗證,在 prisma client 有一個 Extension 可以做到,至於怎麼做到的我們接著看以下的 Demo

$extends 中有一個 query fields 裡面有各種我們的 model key ,這邊因為我們要驗證 user create ,所以我們就驗證 user 底下的 methods

const prismaClient = new PrismaClient().$extends({
  query: {
    user: {
      create: async ({ args, query }) => {
        args.data = await userCreateInputSchema.parseAsync(args.data)
        return query(args)
      },
      update: async ({ args, query }) => {
        args.data = await userCreateInputSchema.parseAsync(args.data)
        return query(args)
      },
      updateMany: async ({ args, query }) => {
        args.data = await userCreateInputSchema.partial().parseAsync(args.data)
        return query(args)
      },
      upsert: async ({ args, query }) => {
        args.create = await userCreateInputSchema.parseAsync(args.create)
        args.update = await userCreateInputSchema.partial().parseAsync(args.update)
        return query(args)
      },

    }
  }
})

每個 methods 都有兩個屬性,argsqueryargs 會是在使用 prisma client 時你 inputvalue

例如我們在 create User 時候我們會用 create apidata 輸入的 object 像是 firstName 等等他等同於 args.data ,所以我們才會用userCreateInputSchema.parseAsyncparse args.data

 const user = await prismaClient.user.create({
      data: {
        firstName: 'Danny',
        lastName: 'Wu',
        email: "hiunji64@gmail.com"
      }
    })

query(args) 就是讓 prisma 去執行我們輸入的 input

create: async ({ args, query }) => {
        args.data = await userCreateInputSchema.parseAsync(args.data)
        return query(args)
      }

然後我們寫一個 main function 去測試我們 prisma$extends 結果

const main = async () => {
  try {

    const user = await prismaClient.user.create({
      data: {
        firstName: 'Danny',
        lastName: 'Wu',
        email: "sdfsdf@gmail.com"
      }
    })
  } catch (error) {
    if (error instanceof ZodError) {

      console.log(error.formErrors)
    }
  }

}

你會看到當我輸入一個不存在 DBemail 時候,會自動跳出 This email is not in our databasemessage 去提醒你的 input 是無效的

{
  formErrors: [],
  fieldErrors: { email: [ 'This email is not in our database' ] }
}

甚至如果 email 格式不對,也會跳 zod error 提醒你格式要符合 email

 const user = await prismaClient.user.create({
      data: {
        firstName: 'Danny',
        lastName: 'Wu',
        email: "sdfsdf"
      }
    })
{
  formErrors: [],
  fieldErrors: {
    email: [
      'this is not validate emails',
      'This email is not in our database'
    ]
  }
}

所以如果輸入合法的 email 的話就可以成功 create user

const user = await prismaClient.user.create({
      data: {
        firstName: 'Danny',
        lastName: 'Wu',
        email: "hiunji64@gmail.com"
      }
    })

看到以下的 result 就代表你成功了~

{
  id: 4,
  email: 'hiunji64@gmail.com',
  firstName: 'Danny',
  lastName: 'Wu',
  fullName: 'Danny Wu'
}

Computed fields

最後加碼一個內容就是,有的時候我們可能需要根據特定的欄位去結合我們要的資料,但又不想動到 DB 的欄位的時候,我們可以使用 Computed 的功能,舉個例子來說,以我們的 User Model 為例子,我們有 firstNamelastName ,我們很常會需要一個 fullName 的資料呈現,這時用 Computed 就很適合,使用的方式很簡單我們一樣要在 $extends 加上一個 result 欄位:

const prismaClient = new PrismaClient().$extends({
  query: {
    user: {
      create: async ({ args, query }) => {
        args.data = await userCreateInputSchema.parseAsync(args.data)
        return query(args)
      },
      update: async ({ args, query }) => {
        args.data = await userCreateInputSchema.parseAsync(args.data)
        return query(args)
      },
      updateMany: async ({ args, query }) => {
        args.data = await userCreateInputSchema.partial().parseAsync(args.data)
        return query(args)
      },
      upsert: async ({ args, query }) => {
        args.create = await userCreateInputSchema.parseAsync(args.create)
        args.update = await userCreateInputSchema.partial().parseAsync(args.update)
        return query(args)
      },

    }
  },
  result: {
    user: {
      fullName: {
        needs: { firstName: true, lastName: true },
        compute: (user) => {
          return `${user.firstName} ${user.lastName}`
        }
      }
    }
  }
})
  • result : 用來管理 model return 的內容
  • needs : 表示哪些欄位必須要有 value 你才能夠 compute
  • compute : compute return 的結果

之後我們 query 一下 data

 const user = await prismaClient.user.findFirst({
      where: {
        email: 'hiunji64@gmail.com'
      }
    })

你會看到我們成功多一個 fullName 欄位了,是不是很開心又激動呢~

{
  id: 1,
  email: 'hiunji64@gmail.com',
  firstName: 'Danny',
  lastName: 'Wu',
  fullName: 'Danny Wu'
}

大家如果有問題可以來小弟的群組討論~

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day17. 一些讓你看來很強的 ORM - prisma (Validator)下
下一篇
Day19. 一些讓你看來很強的 ORM - prisma (customer methods)
系列文
一些讓你看來很強的 ORM - prisma30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言